package com.htfyun.serialport.uartservo;

import com.htfyun.serialport.core.OnSerialPortDataListener;
import com.htfyun.serialport.core.SerialPortManager;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public abstract class IServoControl implements OnSerialPortDataListener {

    protected IServoCommand servoCommand;
    protected IServoReadDataAnalyze readDataAnalyze;
    protected UUID uuidToken;

    protected final ServoInterestedInfo servoInterestedInfo;

    private List<OnServoListener> servoListenerList = new CopyOnWriteArrayList<>();

    public static final int DEFAULT_FEEDBACK_TIME_100ms = 2; //ms
    public static final int DEFAULT_ANGULAR_VELOCITY = 7;//AngularVelocity

    public IServoControl(IServoCommand command, IServoReadDataAnalyze analyze) {
        servoCommand = command;
        readDataAnalyze = analyze;

        servoInterestedInfo = new ServoInterestedInfo();

        servoInit();
    }

    public void release() {
        servoListenerList.clear();
        SerialPortManager.INSTANCE.unregisterSerialPortDataListener(uuidToken);
        uuidToken = null;
    }


    public void registerServoListener(OnServoListener listener) {
        if (!servoListenerList.contains(listener)) {
            servoListenerList.add(listener);
        }
    }

    public void unregisterServoListener(OnServoListener listener) {
        servoListenerList.remove(listener);
    }

    protected void onServoListenerAction(List<ServoErrorAttr> errorAttrList,
                                         ServoControlAttr controlAttr,
                                         ServoControlAttr.Instruction instruction) {

        if (servoListenerList.isEmpty()) {
            return;
        }

        for (OnServoListener listener : servoListenerList) {
            listener.onServoListener(errorAttrList, controlAttr, instruction, servoInterestedInfo);
        }

    }

    protected void writeServo(byte[] bytes) {

        if (uuidToken == null) {
            uuidToken = SerialPortManager.INSTANCE.registerSerialPortDataListener(this);
        }
        SerialPortManager.INSTANCE.writeSerialPort(uuidToken, bytes);
    }


    public ServoInterestedInfo getServoInterestedInfo() {

        return servoInterestedInfo;
    }

    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}
     */
    public void getFirmwareVersion() {
        servoControlGet(ServoControlAttr.firmware_version);
    }

    /**
     * 设置舵机位置; 0~360
     *
     * @param position 值范围[0, 360]
     */
    public void setPosition(int position) {
        servoControlSet(ServoControlAttr.position, position);
    }

    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}
     */
    public void getPosition() {
        servoControlGet(ServoControlAttr.position);
    }

    /**
     * 获取当前 位置. position 是保存在 {@link ServoInterestedInfo}
     *
     * @return value 范围 [0-360]
     */
    public int getCurrentPosition() {
        return servoInterestedInfo.getPosition();
    }

    /**
     * 设置角速度, 范围[1, 100]
     *
     * @param velocity 值范围[1, 100]<p>
     *                 如果这个值小于等于0, 那么，舵机将以电压 能提供的最大速度运动
     */
    public void setAngularVelocity(int velocity) {
        servoControlSet(ServoControlAttr.angular_velocity, velocity);
    }

    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}
     */
    public void getAngularVelocity() {
        servoControlGet(ServoControlAttr.angular_velocity);
    }

    public int getCurrentAngularVelocity() {
        return servoInterestedInfo.getAngularVelocity();
    }

    /**
     * 设置舵机最小角度限制; 0~360
     *
     * @param angleAreaMin 值范围[0, 360]
     */
    public void setAngleAreaMin(int angleAreaMin) {
        servoControlSet(ServoControlAttr.angle_area_min, angleAreaMin);
    }

    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}
     */
    public void getAngleAreaMin() {
        servoControlGet(ServoControlAttr.angle_area_min);
    }

    /**
     * 获取当前 位置. angleAreaMin 是保存在 {@link ServoInterestedInfo}
     *
     * @return value 范围 [0-360]
     */
    public int getCurrentAngleAreaMin() {
        return servoInterestedInfo.getAngleAreaMin();
    }

    /**
     * 设置舵机最大角度限制; 0~360
     *
     * @param angleAreaMax 值范围[0, 360]
     */
    public void setAngleAreaMax(int angleAreaMax) {
        servoControlSet(ServoControlAttr.angle_area_max, angleAreaMax);
    }

    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}
     */
    public void getAngleAreaMax() {
        servoControlGet(ServoControlAttr.angle_area_max);
    }

    /**
     * 获取当前 位置. angleAreaMax 是保存在 {@link ServoInterestedInfo}
     *
     * @return value 范围 [0-360]
     */
    public int getCurrentAngleAreaMax() {
        return servoInterestedInfo.getAngleAreaMax();
    }


    /**
     * 设置舵机扭矩限制; 0~100
     *
     * @param limit 值范围[0, 100]
     */
    public void setTorqueLimit(int limit) {
        servoControlSet(ServoControlAttr.torque_limit, limit);
    }

    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}
     */
    public void getTorqueLimit() {
        servoControlGet(ServoControlAttr.torque_limit);
    }

    /**
     * 是否使能扭矩.
     *
     * @param enabled true: 使能; false: 不使能
     */
    public void setTorqueEnabled(boolean enabled) {
        servoControlSet(ServoControlAttr.torque_active, enabled ? 1 : 0);
    }

    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}
     */
    public void getTorqueEnabled() {
        servoControlGet(ServoControlAttr.torque_active);
    }


    /**
     * 由于串口是乒乓模式, 发送了获取命令后, 需要等待串口返回数据;<p>
     * 在收到读回调后, 请在{@link ServoInterestedInfo 里获取相应信息}<p>
     * <p>
     * 获取温度
     */
    public void getTemperature() {
        servoControlGet(ServoControlAttr.temperature);
    }

    /**
     * 当前马达是否在运动中.
     * @return
     */
    public boolean isCurrentMoving() {
        return servoInterestedInfo.isMoving();
    }

    public abstract void servoStop();

    /**
     * 舵机转动
     *
     * @param clockWise        true:顺时针(0->360, 向右);false:逆时针(360->0,向左).
     * @param relativePosition 相对角度. 范围是(-360~360)
     */
    public abstract void turn(boolean clockWise, int relativePosition);

    /**
     * 一直朝右方向转
     */
    public void turnRight() {
        turnRight(360);
    }

    /**
     * 朝右转relativePosition个角度
     *
     * @param relativePosition 范围是(-360~360), 如果是负数, 那么就是朝左转了
     */
    public void turnRight(int relativePosition) {
        turn(true, relativePosition);

    }

    /**
     * 一直朝左方向转
     */
    public void turnLeft() {
        turnLeft(360);
    }

    /**
     * 朝右转relativePosition个角度
     *
     * @param relativePosition 范围是(-360~360), 如果是负数, 那么就是朝右转了
     */
    public void turnLeft(int relativePosition) {
        turn(false, relativePosition);

    }

    /**
     * 设置0点
     *
     */
    public void setZeroForServo() {
        servoControlSet(ServoControlAttr.set_zero, 1);
    }

    /**
     * 大约每个多少 ms 主动上报 position 和 moving status
     * @param time_100ms 如果设置是1, 则是100ms上报, 如是2, 则是200ms上报; 0 表示不上报
     */
    public void setFeedbackTime_100ms(int time_100ms) {
        servoControlSet(ServoControlAttr.set_feedback_time, time_100ms);
    }

    public void getFeedbackTime_100ms() {
        servoControlGet(ServoControlAttr.set_feedback_time);
    }

    public int getCurrentFeedbackTime_100ms() {
        return servoInterestedInfo.getFeedbackTime_100ms();
    }

    public boolean isFeedbackEnabled() {
        return servoInterestedInfo.getFeedbackTime_100ms() > 0;
    }

    //////////////////////////

    /**
     * 因为有些马达要进行值转换. 所以不能直接调用.
     *
     * @param controlAttr
     * @param value
     */
    private void servoControlSet(ServoControlAttr controlAttr, int value) {
        servoControl(controlAttr, ServoControlAttr.Instruction.set, value);
    }

    private void servoControlGet(ServoControlAttr controlAttr) {
        servoControl(controlAttr, ServoControlAttr.Instruction.get, 0);
    }

    //////////////////////////
    protected abstract void servoControl(ServoControlAttr controlAttr, ServoControlAttr.Instruction instruction, int value);

    protected abstract void servoInit();

}
